/*
 * @author Kaung Htet
 */
package com.khzaw.textbuddy;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.util.Scanner;

public class TextBuddy {

	// error messages 
	private static final String ERROR_EMPTY_COMMAND = "Command type string cannot be null";
	private static final String ERROR_INVALID_COMMAND = "Unrecognized command.";
	private static final String ERROR_INVALID_ARG = "Invalid arguments";
	private static final String ERROR_INVALID_PARAM = "Invalid parameter";
	private static final String ERROR_NO_ARG = "Please specify the path of text file";
	private static final String ERROR_FILE_CREATION = "Error creating the text file";
	private static final String ERROR_FILE_IO = "Error in file I/O";
	private static final String ERROR_FILE_NOT_FOUND = "File not found!";
	private static final String ERROR_DELETE = "Error occurred while deleting";
	private static final int ERROR_INVALID_LINE_NO = -1;
	
	// success messages
	private static final String SUCCESS_ADD = "added to %1$s \"%2$s\"";
	private static final String SUCCESS_CLEAR = "all content deleted from %s";
	private static final String SUCCESS_DELETE = "deleted from %1$s: \"%2$s\"";
	
	// size of arguments and parameters
	private static final int ARG_SIZE = 1;
	
	// index
	private static final int INDEX_COMMAND = 0;
	private static final int INDEX_PARAMS = 1;
	private static final int INDEX_FILE_ARG = 0;
	
	// misc texts
	private static final String TXT_EMPTY_FILE = "%s is empty";
	
	// msg
	private static final String MSG_WELCOME = "Welcome to TextBuddy. %s is ready for use";

	private static Scanner scanner = new Scanner(System.in);
	
	private static File file;
	private static String filename = null;
	private static BufferedReader reader;
	private static PrintWriter writer;
	
	// supported commands
	enum COMMAND_TYPE {
		ADD, DISPLAY, DELETE, CLEAR, INVALID, EXIT
	}

	public static void main(String[] args) {
		System.out.println(handleFileInput(args));
		while(true) {
			System.out.print("command:");
			String userCommand = scanner.nextLine();
			executeCommand(userCommand);
		}
	}
	
	private static String handleFileInput(String[] args) {
		handleArgErrors(args);
		filename = args[INDEX_FILE_ARG];
		file = new File(filename);
		if(!file.isFile()) {
			try {
				file.createNewFile();
			} catch(Exception e) {
				throw new Error(ERROR_FILE_CREATION);
			}
		}
		return String.format(MSG_WELCOME, filename);
	}
	
	private static void handleArgErrors(String[] args) {
		if(args.length > ARG_SIZE) {
			throw new Error(ERROR_INVALID_ARG);
		} else if(args.length < ARG_SIZE) {
			throw new Error(ERROR_NO_ARG);
		}
	}
	
	private static void executeCommand(String command) {
		String input[] = command.split("\\s+", 2);
		String function = input[INDEX_COMMAND];		// the first word indicates the commandType
		String params = (input.length > 1 ? input[INDEX_PARAMS] : null);

		COMMAND_TYPE commandType = determineCommandType(function);
		switch(commandType) {
		case ADD:
			 addFunction(params); 	break;
		case DISPLAY:
			 displayFunction(params); break;
		case DELETE:
			 deleteFunction(params); break;
		case CLEAR:
			 clearFunction(params); break;
		case INVALID:
			 invalidFunction(params); break;
		case EXIT:
             exitFunction();
		default:
			throw new Error(ERROR_INVALID_COMMAND);
		}
	}
	
	private static COMMAND_TYPE determineCommandType(String commandType) {
		if(commandType == null) {
			throw new Error(ERROR_EMPTY_COMMAND);
		}

		if(commandType.equalsIgnoreCase("add")) {
			return COMMAND_TYPE.ADD;
		} else if(commandType.equalsIgnoreCase("display")) {
			return COMMAND_TYPE.DISPLAY;
		} else if(commandType.equalsIgnoreCase("delete")) {
			return COMMAND_TYPE.DELETE;
		} else if(commandType.equalsIgnoreCase("clear")) {
			return COMMAND_TYPE.CLEAR;
		} else if(commandType.equalsIgnoreCase("exit")) {
			return COMMAND_TYPE.EXIT;
		} else {
			return COMMAND_TYPE.INVALID;
		}

	}
	
	/*
	 * append a new line at the end of the text file
	 */
	private static void addFunction(String command) {
		if(command == null) {
			System.out.println(ERROR_INVALID_PARAM);
			return;
		}
		try {
			writer = new PrintWriter(new BufferedWriter(new FileWriter(filename, true)));
			writer.println(command);
			writer.close();
			System.out.println(String.format(SUCCESS_ADD, filename, command));
		} catch(IOException e) {
			throw new Error(ERROR_FILE_IO);
		}
	}
	
	/*
	 * read the file and display the content line by line
	 */
	private static void displayFunction(String command) {
		if(command != null) {
			System.out.println(ERROR_INVALID_PARAM);
			return;
		}
		if(file.length() == 0) {
			System.out.println(String.format(TXT_EMPTY_FILE, filename));
			return;
		}
		try {
			reader = new BufferedReader(new FileReader(filename));
			int lineNo = 1;
			String line = null;
			while((line = reader.readLine()) != null) {
				System.out.println(String.format("%1$d. %2$s", lineNo++, line));
			}
			reader.close();
		} catch(IOException e) {
			throw new Error(ERROR_FILE_IO);
		}
	}
	
	/*
	 * deletes a specified line number from the file
	 * 	nth special, read it with a stream and write it to another stream
	 *  skip said line number
	 *  replace the old file with the new file
	 */
	private static void deleteFunction(String command) {
		if(command == null) {
			System.out.println(ERROR_INVALID_PARAM);
			return;
		}
		int lineNo = determineLineNo(command);
		if(lineNo == ERROR_INVALID_LINE_NO) {
			return;
		}
		boolean isValidNo = isLineNoValid(lineNo);
		if(isValidNo) {
			String TMP_FILE = filename + "_temp";
			String stringToDelete = null;
			try {
				int count = 1;
				reader = new BufferedReader(new FileReader(filename));
				writer = new PrintWriter(new BufferedWriter(new FileWriter(TMP_FILE)));
				String line = null;
				while((line = reader.readLine()) != null) {
					if(lineNo != count) {
						writer.println(line);
					} else {
						stringToDelete = line;
					}
					count++;
				}
				reader.close();
				writer.close();
			} catch(IOException e) {
				throw new Error(ERROR_FILE_IO);
			}
			
			replaceOriginalFile(filename, TMP_FILE);
			System.out.println(String.format(SUCCESS_DELETE, filename, stringToDelete));
		} else {
            System.out.println(ERROR_DELETE);
		}
	}

	/*
	 * delete the file and replace with a new file (same name)
	 */
	private static void clearFunction(String command) {
		if(command != null) {
			System.out.println(ERROR_INVALID_PARAM);
			return;
		}
		file = new File(filename);
		if(file.isFile()) {
			file.delete();
		}
		try {
			file.createNewFile();
		} catch(IOException e) {
			throw new Error(ERROR_FILE_CREATION);
		}
		System.out.println(String.format(SUCCESS_CLEAR, filename));
	}
	
	private static void invalidFunction(String command) {
		System.out.println(ERROR_INVALID_COMMAND);
	}
	
	private static void exitFunction() {
		System.exit(0);
	}

	/*
	 * replace the original file and replace it with the tmp file with a line removed
	 * @param orig
     * 			path of the original file
	 * 		   tmp
     *        	path of the tmp file
	 */
	private static void replaceOriginalFile(String orig, String tmp) {
		File oldFile = new File(orig);
		oldFile.delete();
		File newFile = new File(tmp);
		newFile.renameTo(new File(orig));
	}

	/*
	 * check to ensure that the given line number doesn't exceed to the total number of lines
	 * in the file
	 */
	private static boolean isLineNoValid(int num) {
		int totalLines = ERROR_INVALID_LINE_NO;
		try {
			LineNumberReader reader = new LineNumberReader(new FileReader(new File(filename)));
            reader.skip(Long.MAX_VALUE);
            totalLines = reader.getLineNumber();
            reader.close();
		} catch(FileNotFoundException e)  {
			throw new Error(ERROR_FILE_NOT_FOUND);
		} catch(IOException e) {
			throw new Error(ERROR_FILE_IO);
		}
		return (num <= totalLines && num > 0);
		
	}
	
	/*
	 * make sure the given parameter is really a number
	 */
	private static int determineLineNo(String command) {
		try {
			int lineNo = Integer.parseInt(command);
			if(lineNo > 0) {
				return lineNo;
			} else {
				return ERROR_INVALID_LINE_NO;
			}
		} catch(NumberFormatException e) {
			System.out.println(ERROR_INVALID_PARAM);
		}
		return ERROR_INVALID_LINE_NO;

	}
}